package biolabs.mcl.syntax

import scala.util.matching.Regex
import scala.util.parsing.combinator.JavaTokenParsers

class MCLParsers extends JavaTokenParsers {

	lazy val modelDefinition: Parser[ModelDefinitionAST] =
		(opt("abstract") <~ "model") ~ ident ~ typeParams ~ constructorParams ~ superTypeSpecs ~ participations ~ containments ~ performedActions <~ opt(";") ^^ {
			case ~(~(~(~(~(~(~(abs, id), tParams), fParams), superTypes), participations), containings), actions) =>
				new ModelDefinitionAST(id, abs.isDefined, tParams, fParams, superTypes, participations, containings, actions)
		}

	/** Returns a regular expression that matches a text without any type
	  * of parenthesis or string literals, i.e. is safe with respect to
	  * parenthesizing. In addition, it contains no characters from the
	  * given string of forbidden characters.
	  * @param forbiddenChars string of characters that should be excluded
	  * by the resulting regular expression, in addition to different kinds
	  * of parentheses. It will be used inside a character set (`[...]`)
	  * in a regular expression. Any characters that need escaping in this
	  * place in the regular expression should be properly escaped.
	  */
	private def safeText(forbiddenChars: String = ""): Regex =
		new Regex("[^" + forbiddenChars + """"(){}\[\]""" + "]+")

	private lazy val typeParams: Parser[List[TypeParamAST]] =
		"[" ~> rep1sep(typeParam, ",") <~ "]" | "" ^^^ { List() }

	private lazy val typeParam: Parser[TypeParamAST] = (
		ident ~ upperBound ^^ { case ~(a, b) => UpperBoundedTypeParamAST(a, b) }
		| ident ^^ { UnboundedTypeParamAST(_) }
	)

	private lazy val upperBound = "<:" ~> multiType

	private lazy val constructorParams: Parser[List[ConstructorParamAST]] =
		"(" ~> repsep(constructorParam, ",") <~ ")" | "" ^^^ { List() }

	private lazy val constructorParam: Parser[ConstructorParamAST] =
		opt("-") ~ typedIdent ^^ {
			case ~(minus, TypedIdentifierAST(id, t)) => ConstructorParamAST(id, t, minus.isEmpty)
		}

	private lazy val typedIdent: Parser[TypedIdentifierAST] =
		(ident <~ ":") ~ multiType ^^ { case ~(id, tpe) => TypedIdentifierAST(id, tpe) }

	private lazy val multiType: Parser[MultiTypeAST] =
		rep1sep(singleType, "with") ^^ { new MultiTypeAST(_) }

	private lazy val dotIdent: Parser[String] =
		rep1sep(ident, ".") ^^ { _ mkString "." }

	private lazy val singleType: Parser[SingleTypeAST] =
		dotIdent ~ typeArgs ^^ {
			case ~(id, args) => new SingleTypeAST(id, args)
		}

	private lazy val typeArgs: Parser[List[MultiTypeAST]] =
		"[" ~> rep1sep(multiType, ",") <~ "]" | "" ^^^ { List() }

	private lazy val superTypeSpecs: Parser[List[SuperTypeSpecAST]] = (
		"combines" ~> rep1sep(superTypeSpec, "with")
		| "" ^^^ { List() }
	)

	private lazy val superTypeSpec: Parser[SuperTypeSpecAST] =
		singleType ~ funcArgs ~ opt(timeScaleSpec) ^^ {
			case ~(~(singleType, args), timeScaleSpec) =>
				new SuperTypeSpecAST(singleType, args, timeScaleSpec)
		}

	private lazy val participations: Parser[List[ParticipationAST]] = (
		"participates" ~> "in" ~> rep1sep(participation, ",")
		| "" ^^^ { List() }
	)

	private lazy val participation: Parser[ParticipationAST] =
		singleType ~ ("as" ~> delegateCombination) ^^ {
			case ~(action, dc) => ParticipationAST(action, dc)
		}

	private lazy val delegateCombination: Parser[DelegateCombinationAST] = (
		delegateDesignation ^^ { SimpleDelegateCombinationAST(_) }
		| ("(" ~> rep1sep(delegateDesignation, ",") <~ ")") ~ ("combined" ~> "by" ~> wellParenthesizedText(",")) ^^ {
			case ~(sources, combinator) => MultiDelegateCombinationAST(sources, combinator)
		}
	)

	private lazy val delegateDesignation: Parser[DelegateDesignationAST] =
		dotIdent ~ opt(":" ~> singleType) ^^ {
			case ~(superTypeID, actionType) => new DelegateDesignationAST(superTypeID, actionType)
		}

	private lazy val performedActions: Parser[List[PerformedActionAST]] = (
		"performs" ~> rep1sep(performedAction, ",")
		| "" ^^^ { List() }
	)

	private lazy val performedAction: Parser[PerformedActionAST] =
		singleType ~ funcArgs ~ opt(actionTimeSpec) ^^ {
			case ~(~(typeSpec, args), actionTimeSpec) =>
				new PerformedActionAST(typeSpec, args, actionTimeSpec)
		}

	private lazy val actionTimeSpec: Parser[String] =
		"@" ~> "(" ~> wellParenthesizedText("") <~ ")"

	private lazy val timeScaleSpec: Parser[(String, String)] =
		( "%" ~> "(" ~> wellParenthesizedText(":") <~ ":" ) ~
				( wellParenthesizedText(":") <~ ")" ) ^^ {
			case ~(a, b) => (a, b)
		}

	private lazy val containments: Parser[List[ContainmentAST]] = (
		"contains" ~> rep1sep(containment, ",")
		| "" ^^^ { List() }
	)

	private lazy val containerType: Parser[ContainerSpecAST] =
		dotIdent ~ funcArgs ^^ {
			case ~(className, args) => new ContainerSpecAST(className, args)
		}

	private lazy val associations: Parser[List[TypedIdentifierAST]] = (
		"associated" ~> "with" ~> (
			typedIdent ^^ { List(_) }
			| "("~>rep1sep(typedIdent, ",")<~")"
		)
		| "" ^^^ { List() }
	)

	private lazy val containment: Parser[ContainmentAST] = (
		typedIdent ~ (
			"=" ~> wellParenthesizedText("%") ^^ { case expr => Left(expr) } |
			"..." ~> associations ~ opt("in" ~> containerType) ^^ { case ~(assoc, ct) => Right((assoc, ct)) }
		) ~ opt(timeScaleSpec) ^^ {
			case ~(~(TypedIdentifierAST(id, t), Left(initExpr)), ts) => SingletonContainmentAST(id, t, initExpr, ts)
			case ~(~(TypedIdentifierAST(id, t), Right((assoc, ct))), ts) => MultiContainmentAST(id, t, assoc, ts, ct)
		}
	)

	private lazy val funcArgs: Parser[List[String]] =
		"(" ~> repsep(wellParenthesizedText(","), ",") <~ ")" | "" ^^^ { List() }

	private def wellParenthesizedText(forbiddenTopLevelChars: String, forbiddenNestedChars: String = ""): Parser[String] =
		wellParenthesizedText(safeText(forbiddenTopLevelChars), safeText(forbiddenNestedChars))

	private def wellParenthesizedText(safeTopLevelText: Regex, safeNestedText: Regex): Parser[String] = (
		opt(safeTopLevelText) ~ (
			stringLiteral
			| parenText(safeNestedText)
			| braceText(safeNestedText)
			| bracketText(safeNestedText)
		) ~ opt(wellParenthesizedText(safeTopLevelText, safeNestedText)) ^^ {
			case ~(~(Some(a), b), Some(c)) => a + b + c
			case ~(~(Some(a), b), None)    => a + b
			case ~(~( None  , b), Some(c)) => b + c
			case ~(~( None  , b), None)    => b
		}
	) | safeTopLevelText

	private def parenText(safeText: Regex): Parser[String] =
		"(" ~> opt(wellParenthesizedText(safeText, safeText)) <~ ")" ^^ {
			case Some(text) => "(" + text + ")"
			case _ => "()"
		}

	private def braceText(safeText: Regex): Parser[String] =
		"{" ~> opt(wellParenthesizedText(safeText, safeText)) <~ "}" ^^ {
			case Some(text) => "{" + text + "}"
			case _ => "{}"
		}

	private def bracketText(safeText: Regex): Parser[String] =
		"[" ~> opt(wellParenthesizedText(safeText, safeText)) <~ "]" ^^ {
			case Some(text) => "[" + text + "]"
			case _ => "[]"
		}
}